#pragma once

#include <iostream>
#include <pthread.h>
#include <vector>
#include "Mutex.hpp"
#include "Sem.hpp"

// 这里是使用循环队列 + POSIX信号量 来进行生产者消费者模式 -> 直接写多生产，多消费的模式(其实就是多了两把锁)

using namespace myMutex;
using namespace mySem;

// 信号量的值（生产端先开始） 这里定义为5，方便测试

template<class T, size_t N = 5>
class Queue_cycle{
public:
    Queue_cycle()
        :_capacity(N),
        _productor_sem(N),
        _productor_pos(0),
        _consumer_sem(0),
        _consumer_pos(0),
        _queue_cycle(_capacity, T())
    {}


    ~Queue_cycle(){}

    void Push(const T& data){
        // 生产者在这里进行生产

        // 1.首先需要申请信号量，即生产方预定资源 只有申请到了生产的信号量(能放)才能继续生产入队
        _productor_sem.P();
        {
        // 多线程的情况下要加锁 这个Lock_Guard生命周期在代码块内部！一旦出去就自动解锁了
            Lock_Guard guard(_productor_lock);

        //2.入队
            _queue_cycle[_productor_pos] = data;
        //3.调整生产位置
            ++_productor_pos;
        //4.但是，由于是环形队列，所以需要维持环形队列的特性 -> 防止越界
            _productor_pos %= _capacity;
        }
        //5.这个时候，生产的信号量-1，但是拿数据的信号量就可以加1
        _consumer_sem.V();
    }

    T Pop(){
        // 消费者进行消费

        // 1.首先需要申请信号量，即消费方预定消费资源 只有申请到了要消费的信号量(能拿)才能从队中取数据
        _consumer_sem.P();

        T data;

        {
            Lock_Guard guard(_consumer_lock);

        //2. 出队
            data = _queue_cycle[_consumer_pos];
        //3. 调整消费位置
            ++_consumer_pos;
        //4.但是，由于是环形队列，所以需要维持环形队列的特性 -> 防止越界
            _consumer_pos %= _capacity;
        }
        //5.这个时候，消费的信号量-1，但是放数据的信号量就可以加1
        _productor_sem.V();
        return data;
    }


private:
    Sem _productor_sem;
    int _productor_pos;

    Sem _consumer_sem;
    int _consumer_pos;

    size_t _capacity;
    std::vector<T> _queue_cycle;
    

    Mutex _productor_lock;
    Mutex _consumer_lock;
};


// 这里稍微解释一下：
// 首先，环形队列的消费者生产者模式需要满足几个基本条件
//    1.队列为满 / 空，就会导致当前的两个下标相同 int _productor_pos; int _consumer_pos;
//    2.但是，当两个下标在一起的时候，需要做一些判断：
        // 队列满了，生产方不能再生产，只能让消费方先消费
        // 队列空了，消费方无法消费，只能先让生产方先生产
//    3.在一个循环队列下，生产方不能给消费方完整的套一圈 -> 防止未消费的数据被覆盖
//    4.在一个循环队列下，消费方不能超过生产方，因为没有生产时无法消费

// 细节：
//    循环队列下，生产方和消费方只有在队列满了/空了的时候，两个下标重叠，此时生产方和消费方互斥！
//    其余时候，两个下标不可能重叠，所以消费方和生产方可以并发处理！（这是生产方和消费方的关系）


// 上面说的所有条件，都可以使用信号量来控制！ (以队列大小为N)
// 队列满了：生产方信号量N， 消费方为0 -> 消费方阻塞在申请信号量处
// 队列空了：消费方信号量0， 生产方为N -> 生产方阻塞在申请信号量处
// 在信号量的控制下，是不可能有”套圈“的行为出现的！
// 而且，在信号量的控制下，生产方和消费方的线程的在队列的操作下标是不一样的！互不影响！所以可以并发！



// 多生产——多消费的情况：
// 在单——单情况下，不需要维护多个consumer之间的关系，不需要维护多个productor之间的关系 cp之间关系通过信号量维护
// 但在多——多情况下，需要维护c之间，p之间关系！循环队列也是共享资源，所以需要加锁！
    // 加锁的位置在信号量的申请前后都可以！(使用RAII思想加锁解锁)
    // 在申请信号量前加锁，就相当于现在多个线程间选取一个出来，再来申请信号量！ -> 先排队买票，然后再排队进入场所
    // 在申请信号量后加锁，就相当于 -> 现在网上买票，再来排队进入场所
        // 那么肯定是第二种方式效率更高！ 而且，没有预定资源(申请信号量本质)，就没有必要排队！